/*
 * CC3MeshNode.m
 *
 * $Version: cocos3d 0.5.2 (f3df37821a3f) on 2011-03-13 $
 * Author: Bill Hollings
 * Copyright (c) 2010-2011 The Brenwill Workshop Ltd. All rights reserved.
 * http://www.brenwill.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * http://en.wikipedia.org/wiki/MIT_License
 * 
 * See header file CC3MeshNode.h for full API documentation.
 */

#import "CC3MeshNode.h"
#import "CC3BoundingVolumes.h"
#import "CC3OpenGLES11Engine.h"
#import "CGPointExtension.h"
#import "CC3VertexArrayMeshModel.h"


@interface CC3Node (TemplateMethods)
-(void) populateFrom: (CC3Node*) another;
-(void) updateBoundingVolume;
@end

@interface CC3MeshNode (TemplateMethods)
-(void) configureDrawingParameters;
-(void) configureNormalization;
@end

@implementation CC3MeshNode

@synthesize meshModel, material, pureColor;

-(void) dealloc {
	[meshModel release];
	[material release];
	[super dealloc];
}

// Sets the mesh model then, if a bounding volume exists, forces it to rebuild
// using the new mesh data, or creates a default bounding volume from the mesh model.
-(void) setMeshModel:(CC3MeshModel *) aMeshModel {
	id oldMesh = meshModel;
	meshModel = [aMeshModel retain];
	[oldMesh release];
	if (boundingVolume) {
		[self.boundingVolume buildVolume];
	} else {
		self.boundingVolume = [meshModel defaultBoundingVolume];
	}

}

// After setting the bounding volume, forces it to build its volume from the mesh
-(void) setBoundingVolume:(CC3NodeBoundingVolume *) aBoundingVolume {
	[super setBoundingVolume: aBoundingVolume];
	[self.boundingVolume buildVolume];
}


#pragma mark Material coloring

-(ccColor4F) ambientColor {
	return material ? material.ambientColor : kCCC4FBlackTransparent;
}

-(void) setAmbientColor:(ccColor4F) aColor {
	material.ambientColor = aColor;
	[super setAmbientColor: aColor];	// pass along to any children
}

-(ccColor4F) diffuseColor {
	return material ? material.diffuseColor : kCCC4FBlackTransparent;
}

-(void) setDiffuseColor:(ccColor4F) aColor {
	material.diffuseColor = aColor;
	[super setDiffuseColor: aColor];	// pass along to any children
}

-(ccColor4F) specularColor {
	return material ? material.specularColor : kCCC4FBlackTransparent;
}

-(void) setSpecularColor:(ccColor4F) aColor {
	material.specularColor = aColor;
	[super setSpecularColor: aColor];	// pass along to any children
}

-(ccColor4F) emissionColor {
	return material ? material.emissionColor : kCCC4FBlackTransparent;
}

-(void) setEmissionColor:(ccColor4F) aColor {
	material.emissionColor = aColor;
	[super setEmissionColor: aColor];	// pass along to any children
}


#pragma mark CCRGBAProtocol support

-(ccColor3B) color {
	return material ? material.color : ccc3(CCColorByteFromFloat(pureColor.r),
											CCColorByteFromFloat(pureColor.g),
											CCColorByteFromFloat(pureColor.b));
}

-(void) setColor: (ccColor3B) color {
	material.color = color;

	pureColor.r = CCColorFloatFromByte(color.r);
	pureColor.g = CCColorFloatFromByte(color.g);
	pureColor.b = CCColorFloatFromByte(color.b);

	[super setColor: color];	// pass along to any children
}

-(GLubyte) opacity {
	return material ? material.opacity : CCColorByteFromFloat(pureColor.a);
}

-(void) setOpacity: (GLubyte) opacity {
	material.opacity = opacity;
	pureColor.a = CCColorFloatFromByte(opacity);

	[super setOpacity: opacity];	// pass along to any children
}

-(BOOL) isOpaque {
	return material ? material.isOpaque : (pureColor.a == 1.0f);
}

-(void) setIsOpaque: (BOOL) opaque {
	material.isOpaque = opaque;
	pureColor.a = 1.0f;
	
	[super setIsOpaque: opaque];	// pass along to any children
}


#pragma mark Allocation and initialization

-(id) initWithTag: (GLuint) aTag withName: (NSString*) aName {
	if ( (self = [super initWithTag: aTag withName: aName]) ) {
		pureColor = kCCC4FWhite;
	}
	return self;
}

-(void) createGLBuffers {
	LogTrace(@"%@ creating GL server buffers", self);
	[meshModel createGLBuffers];
	[super createGLBuffers];
}

-(void) deleteGLBuffers {
	[meshModel deleteGLBuffers];
	[super deleteGLBuffers];
}

-(void) releaseRedundantData {
	[meshModel releaseRedundantData];
	[super releaseRedundantData];
}

// Template method that populates this instance from the specified other instance.
// This method is invoked automatically during object copying via the copyWithZone: method.
// A copy is made of the material.
// The mesh model is simply assigned, without creating a copy.
// Both this node and the other node will share the mesh model.
-(void) populateFrom: (CC3MeshNode*) another {
	[super populateFrom: another];
	
	[meshModel release];
	meshModel = [another.meshModel retain];		// retained

	[material release];
	material = [another.material copy];			// retained

	pureColor = another.pureColor;
}

-(void) populateAsCenteredRectangleWithSize: (CGSize) rectSize
							   andPureColor: (ccColor4F) aColor {
	[self populateAsRectangleWithSize: rectSize
							 andPivot: ccp(rectSize.width / 2.0, rectSize.height / 2.0)
						 andPureColor: aColor];
}

-(void) populateAsRectangleWithSize: (CGSize) rectSize
						   andPivot: (CGPoint) pivot
					   andPureColor: (ccColor4F) aColor {
	struct {
		CC3Vector location;
		CC3Vector normal;
		ccTex2F texCoord;
	} *vertices;			// Array of custom structures to hold the interleaved vertex data
	
	NSString* itemName;
	GLfloat xMin = 0.0f - pivot.x;
	GLfloat xMax = rectSize.width - pivot.x;
	GLfloat yMin = 0.0f - pivot.y;
	GLfloat yMax = rectSize.height - pivot.y;
	GLsizei vStride = sizeof(*vertices);
	int vCount = 4;
	
	// Interleave the vertex locations, normals and tex coords
	// Create vertex location array, allocating enough space for the stride of the full structure
	itemName = [NSString stringWithFormat: @"%@-Locations", self.name];
	CC3VertexLocations* locArray = [CC3VertexLocations vertexArrayWithName: itemName];
	locArray.elementStride = vStride;
	locArray.elementOffset = 0;						// offset to location element in vertex structure
	vertices = [locArray allocateElements: vCount];

	// Populate vertex locations in the X-Y plane
	vertices[0].location = (CC3Vector){xMax, yMax, 0.0};
	vertices[1].location = (CC3Vector){xMin, yMax, 0.0};
	vertices[2].location = (CC3Vector){xMax, yMin, 0.0};
	vertices[3].location = (CC3Vector){xMin, yMin, 0.0};
	
	// Create the normal array interleaved on the same element array
	itemName = [NSString stringWithFormat: @"%@-Normals", self.name];
	CC3VertexNormals* normArray = [CC3VertexNormals vertexArrayWithName: itemName];
	normArray.elements = vertices;
	normArray.elementStride = vStride;
	normArray.elementCount = vCount;
	normArray.elementOffset = sizeof(CC3Vector);	// offset to normal element in vertex structure
	
	// Populate normals up the positive Z-axis
	vertices[0].normal = kCC3VectorUnitZPositive;
	vertices[1].normal = kCC3VectorUnitZPositive;
	vertices[2].normal = kCC3VectorUnitZPositive;
	vertices[3].normal = kCC3VectorUnitZPositive;
	
	// Rectangular Mesh model with interleaved vertex arrays
	itemName = [NSString stringWithFormat: @"%@-Mesh", self.name];
	CC3VertexArrayMeshModel* mesh = [CC3VertexArrayMeshModel meshWithName: itemName];
	mesh.interleaveVertices = YES;
	mesh.vertexLocations = locArray;
	mesh.vertexNormals = normArray;
	self.meshModel = mesh;
	self.pureColor = aColor;
}

-(void) populateAsCenteredRectangleWithSize: (CGSize) rectSize
								withTexture: (CC3Texture*) texture
							  invertTexture: (BOOL) shouldInvert {
	[self populateAsRectangleWithSize: rectSize
							 andPivot: ccp(rectSize.width / 2.0, rectSize.height / 2.0)
						  withTexture: texture
						invertTexture: shouldInvert];
}

-(void) populateAsRectangleWithSize: (CGSize) rectSize
						   andPivot: (CGPoint) pivot
						withTexture: (CC3Texture*) texture
					  invertTexture: (BOOL) shouldInvert {
	NSString* itemName;

	// Start as a basic white rectangle of the right size and location.
	[self populateAsRectangleWithSize: rectSize andPivot: pivot andPureColor: kCCC4FWhite];
	
	// Get my mesh model and vertices.
	CC3VertexArrayMeshModel* vamm = (CC3VertexArrayMeshModel*)self.meshModel; 
	CC3VertexLocations* locArray = vamm.vertexLocations;
	
	// Create the tex coord array interleaved on the same element array as the vertex locations
	CC3VertexTextureCoordinates* tcArray = nil;
	itemName = [NSString stringWithFormat: @"%@-Texture", self.name];
	tcArray = [CC3VertexTextureCoordinates vertexArrayWithName: itemName];
	tcArray.elements = locArray.elements;
	tcArray.elementStride = locArray.elementStride;
	tcArray.elementCount = locArray.elementCount;
	tcArray.elementOffset = 2 * sizeof(CC3Vector);	// offset to texcoord element in vertex structure

	// Add the texture coordinates array to the mesh model
	vamm.vertexTextureCoordinates = tcArray;
	
	// Populate the texture coordinate array mapping
	struct {
		CC3Vector location;
		CC3Vector normal;
		ccTex2F texCoord;
	} *vertices = locArray.elements;
	
	vertices[0].texCoord = (ccTex2F){1.0, 1.0};
	vertices[1].texCoord = (ccTex2F){0.0, 1.0};
	vertices[2].texCoord = (ccTex2F){1.0, 0.0};
	vertices[3].texCoord = (ccTex2F){0.0, 0.0};
	
	// Align the texture coordinates to the texture
	if (shouldInvert) {
		[tcArray alignWithInvertedTexture: texture];
	} else {
		[tcArray alignWithTexture: texture];
	}
	
	// Add a material and attach the texture
	itemName = [NSString stringWithFormat: @"%@-Material", self.name];
	self.material = [CC3Material materialWithName: itemName];
	self.material.texture = texture;
}

-(void) populateAsWireBox: (CC3BoundingBox) box withPureColor: (ccColor4F) aColor {
	NSString* itemName;
	CC3Vector boxMin = box.minimum;
	CC3Vector boxMax = box.maximum;
	GLuint vertexCount = 8;

	// Create vertexLocation array.
	itemName = [NSString stringWithFormat: @"%@-Locations", self.name];
	CC3VertexLocations* locArray = [CC3VertexLocations vertexArrayWithName: itemName];
	CC3Vector* vertices = [locArray allocateElements: vertexCount];
	
	// Extract all 8 corner vertices from the box.
	vertices[0] = cc3v(boxMin.x, boxMin.y, boxMin.z);
	vertices[1] = cc3v(boxMin.x, boxMin.y, boxMax.z);
	vertices[2] = cc3v(boxMin.x, boxMax.y, boxMin.z);
	vertices[3] = cc3v(boxMin.x, boxMax.y, boxMax.z);
	vertices[4] = cc3v(boxMax.x, boxMin.y, boxMin.z);
	vertices[5] = cc3v(boxMax.x, boxMin.y, boxMax.z);
	vertices[6] = cc3v(boxMax.x, boxMax.y, boxMin.z);
	vertices[7] = cc3v(boxMax.x, boxMax.y, boxMax.z);
	
	GLuint lineCount = 12;
	GLuint indexCount = lineCount * 2;
	GLubyte indexData[] = {
		0, 1, 1, 3, 3, 2, 2, 0,
		4, 5, 5, 7, 7, 6, 6, 4,
		0, 4, 1, 5, 2, 6, 3, 7,
	};
	itemName = [NSString stringWithFormat: @"%@-Indices", self.name];
	CC3VertexIndices* indexArray = [CC3VertexIndices vertexArrayWithName: itemName];
	indexArray.drawingMode = GL_LINES;
	indexArray.elementType = GL_UNSIGNED_BYTE;
	GLubyte* indices = [indexArray allocateElements: indexCount];
	memcpy(indices, indexData, indexCount * sizeof(indexData[0]));
	
	itemName = [NSString stringWithFormat: @"%@-Mesh", self.name];
	CC3VertexArrayMeshModel* mesh = [CC3VertexArrayMeshModel meshWithName: itemName];
	mesh.vertexLocations = locArray;
	mesh.vertexIndices = indexArray;
	self.meshModel = mesh;
	self.pureColor = aColor;
}


#pragma mark Type testing

-(BOOL) isMeshNode {
	return YES;
}


#pragma mark Drawing

/**
 * If we have a material, delegates to the material to set material and texture state,
 * otherwise, establishes the pure color by turning lighting off and setting the color.
 * One material or color is set, delegates to the mesh model to draw mesh.
 */
-(void) drawLocalContentWithVisitor: (CC3NodeDrawingVisitor*) visitor {
	// Remember current lighting state in case we disable it to apply pure color.
	BOOL lightingWasEnabled = [CC3OpenGLES11Engine engine].serverCapabilities.lighting.value;

	if (visitor.shouldDecorateNode) {
		if (material) {
			[material draw];
		} else {
			[CC3Material unbind];
			[[CC3OpenGLES11Engine engine].serverCapabilities.lighting disable];
			[CC3OpenGLES11Engine engine].state.color.value = pureColor;
		}
	} else {
		[CC3Material unbind];
	}
	[self configureDrawingParameters];
	[meshModel drawWithVisitor: visitor];

	// Re-establish previous lighting state.
	[CC3OpenGLES11Engine engine].serverCapabilities.lighting.value = lightingWasEnabled;
}

/**
 * Configures the drawing parameters.
 *
 * The default implementation configures normalization.
 * Subclasses may override to add additional drawing parameters.
 */
-(void) configureDrawingParameters {
	[self configureNormalization];
}

/** Configures GL scaling of normals, based on whether the scaling of this node is uniform or not. */
-(void) configureNormalization {
	CC3OpenGLES11ServerCapabilities* gles11ServCaps = [CC3OpenGLES11Engine engine].serverCapabilities;
	if (meshModel && meshModel.hasNormals) {
		if (self.isUniformlyScaledGlobally) {
			[gles11ServCaps.rescaleNormal enable];
			[gles11ServCaps.normalize disable];
		} else {
			[gles11ServCaps.rescaleNormal disable];
			[gles11ServCaps.normalize enable];
		}

	} else {
		[gles11ServCaps.rescaleNormal disable];
		[gles11ServCaps.normalize disable];
	}
}

@end


#pragma mark -
#pragma mark CC3LineNode

@interface CC3LineNode (TemplateMethods)
-(void) configureLineProperties;
@end


@implementation CC3LineNode

@synthesize lineWidth, shouldSmoothLines;


#pragma mark Allocation and initialization

-(id) initWithTag: (GLuint) aTag withName: (NSString*) aName {
	if ( (self = [super initWithTag: aTag withName: aName]) ) {
		lineWidth = 1.0f;
		shouldSmoothLines = NO;
	}
	return self;
}

// Template method that populates this instance from the specified other instance.
// This method is invoked automatically during object copying via the copyWithZone: method.
-(void) populateFrom: (CC3LineNode*) another {
	[super populateFrom: another];
	
	lineWidth = another.lineWidth;
	shouldSmoothLines = another.shouldSmoothLines;
}


#pragma mark Drawing

/** Overridden to set the line properties in addition to configuring normalization. */
-(void) configureDrawingParameters {
	[self configureLineProperties];
	[self configureNormalization];
}

-(void) configureLineProperties {
	[CC3OpenGLES11Engine engine].serverCapabilities.lineSmooth.value = shouldSmoothLines;
	[CC3OpenGLES11Engine engine].state.lineWidth.value = lineWidth;
}

@end


#pragma mark -
#pragma mark CC3PlaneNode

@implementation CC3PlaneNode
@end

